import numpy as np
from sklearn.metrics import confusion_matrix, classification_report
import os
import pickle
import sys

# ①　完成数据集的加载、洗牌和初始化等一系列的相关操作，并实现One-Hot编码  3份
# load
x = np.loadtxt(r'../../../../../large_data/finalX/PEPX.txt', delimiter=',')
m = len(x)
y = np.loadtxt(r'../../../../../large_data/finalX/PEPL.txt', delimiter=',')
y -= 1  # 0, 1, 2
# shuffle
np.random.seed(1)
a = np.random.permutation(m)
x = x[a]
y = y[a]
# scale
mu = x.mean()
sigma = x.std()
x -= mu
x /= sigma
# splice
x = np.c_[np.ones(m), x]
# onehot
y_oh = np.zeros([m, 3])
for i, v in enumerate(y):
    y_oh[i, int(v)] = 1

# ②　实现特征数据集和标签数据集的切分，训练集：测试集=3：1  3分
m_train = int(0.75 * m)
x_train, x_test = np.split(x, [m_train])
y_oh_train, y_oh_test = np.split(y_oh, [m_train])


# ③　实现激活函数和其导数的自定义方法  3分
# ④　定义3层网络结构，包括输入层、隐藏层和输出层，并设置相关参数要求学习率0.1  最大迭代次数5000，隐藏层单元数100   3分
# ⑤　实现前向和后向传播过程，包含正确书写代价函数和梯度下降代码  4分
def model(x, theta):
    return x.dot(theta)


def sigmoid(z):
    return 1 / (1 + np.exp(-z))


def cost_func(h, y):
    m = len(h)
    return - 1 / m * np.sum(y * np.log(h) + (1 - y) * np.log(1 - h))


def FP(x, theta1, theta2):
    a1 = x
    z2 = model(a1, theta1)
    a2 = sigmoid(z2)
    z3 = model(a2, theta2)
    a3 = sigmoid(z3)
    return a2, a3


def BP(x, y, theta1, theta2, a2, a3, alpha):
    m = len(x)
    s3 = a3 - y
    s2 = s3.dot(theta2.T) * (a2 * (1 - a2))
    dt2 = 1 / m * a2.T.dot(s3)
    dt1 = 1 / m * x.T.dot(s2)
    theta2 -= alpha * dt2
    theta1 -= alpha * dt1
    return theta1, theta2


def grad(x, y, alpha=0.1, iter0=5000):
    m, n = x.shape
    group = iter0 // 20
    theta1 = np.random.randn(n, 100)
    theta2 = np.random.randn(100, 3)
    j_his = np.zeros(iter0)  # cost function value history in iterations
    for i in range(iter0):
        a2, a3 = FP(x, theta1, theta2)
        j = cost_func(a3, y)
        j_his[i] = j
        if 0 == i % group:
            print(f'#{i + 1} cost function value = {j}')
        theta1, theta2 = BP(x, y, theta1, theta2, a2, a3, alpha)
    if 0 != i % group:
        print(f'#{i + 1} cost function value = {j}')
    return theta1, theta2, j_his, a3


# ⑥　实现准确率的自定义方法，实现训练和预测，并输出测试集的准确率  3分
def score(h, y):
    _h = h.argmax(axis=1)
    _y = y.argmax(axis=1)
    return np.mean(_h == _y)


# train
ver = 'v1.0'
path = sys.argv[0] + '_' + ver + '.tmp.dat'
if os.path.exists(path):
    f = open(path, 'rb')
    theta1, theta2, j_his, h_train = pickle.load(f)
    f.close()
    print('Loaded former training data.')
else:
    print('Training ...')
    theta1, theta2, j_his, h_train = grad(x_train, y_oh_train)
    f = open(path, 'wb')
    pickle.dump([theta1, theta2, j_his, h_train], f)
    f.close()
print(f'Training score = {score(h_train, y_oh_train)}')
# predict
_, h_test = FP(x_test, theta1, theta2)
print(f'Testing score = {score(h_test, y_oh_test)}')


# ⑦　输出混淆矩阵和分类报告  4分
y_lbd_test = y_oh_test.argmax(axis=1)  # target values labeled
h_lbl_test = h_test.argmax(axis=1)  # hypothesis labeled
print('混淆矩阵')
print(confusion_matrix(y_lbd_test, h_lbl_test))
print('分类报告')
print(classification_report(y_lbd_test, h_lbl_test))
